/*
 * ParseFct2.java
 * 
 * Copyright 2004 Christoph Csallner and Yannis Smaragdakis.
 */
package edu.gatech.cc.cnc;

import java.util.Map;

import de.fhg.first.fd.X;
import edu.gatech.cc.cnc.params.Param;
import edu.gatech.cc.cnc.params.ParamArray;
import edu.gatech.cc.cnc.params.ParamBoolean;
import edu.gatech.cc.cnc.params.ParamComplex;
import edu.gatech.cc.cnc.params.ParamInt;
import edu.gatech.cc.cnc.params.ParamIntegral;

/**
 * Methods specific to second pass.
 * 
 * @author csallner@gatech.edu (Christoph Csallner)
 */
public class ParseFct2 extends ParseFct {

  /** Constrain
  *
  * Used to propagate a constrain upwards */
 public static abstract class Constrain extends Result {
 	protected int r = -1;
 	
 	/** Constructor */
 	protected Constrain(int r2) {r = r2;}
 	
 	/** Negate constraint when parsing (NOT constraint). */
 	public void negate() {
 		switch (r) {
				case sym.EQ: 	r=sym.NEQ; break;
				case sym.NEQ: r=sym.EQ; break;
				case sym.L: 	r=sym.GEQ; break;
				case sym.LEQ: r=sym.G; break;
				case sym.G: 	r=sym.LEQ; break;
				case sym.GEQ: r=sym.L; break;
			}
 	}
 	
 	public abstract void apply();
 }	
	
  public static class R2VarConst extends Constrain {
  	protected Param param = null;
  	protected String constant = null;
  	
  	public R2VarConst(Param p, int r2, String c) {
  		super(r2);
  		param = p;
  		constant = c;
  	}
  	
  	@Override
  	public void apply() {
  		Out.debug(param +" "+ rel(r) +" " +constant);
  		param.constrain(r, constant);
  	}
  }
  
  public static class R2ConstVar extends Constrain {
  	protected Param param = null;
  	protected String constant = null;
  	
  	public R2ConstVar(String c, int r2, Param p) {
  		super(r2);
  		constant = c;
  		param = p;
  	}  	
  	
  	@Override
  	public void apply() {
  		Out.debug(constant +" "+ rel(r) +" " +param);
  		param.constrain(constant, r);
  	}
  }
  
  public static class R2VarVar extends Constrain {
  	protected Param param1 = null;
  	protected Param param2 = null;
  	
    public R2VarVar(Param p1, int r2, Param p2) {
    	super(r2);
    	param1 = p1;
  		param2 = p2;
    }
    
    @Override
  	public void apply() {
  		Out.debug(param1 +" "+ rel(r) +" " +param2);
  		param1.constrain(r, param2);
  	}  	
  }
  
  public static class R2XX extends Constrain {
  	protected X lhs = null;
  	protected X rhs = null;
  	
  	public R2XX(X x1, int r2, X x2) {
  		super(r2);
  		lhs = x1;
  		rhs = x2;
  	}
  	
  	@Override
  	public void apply() {
  		Out.debug(lhs +" "+ rel(r) +" " +rhs);
  		constrain(lhs, r, rhs);
  	}
  }
  
  public static class NotVar extends Constrain {
  	protected Param param = null;
  	
  	public NotVar(ParamBoolean p1) {
  		super(sym.EQ);
  		param = p1;
  	}
  	
  	@Override
  	public void apply() {
  		param.constrain(r, "false");
  	}
  }

  
  /**
   * Constructor
   */
	public ParseFct2 (Map<String,Param> params) {
		super(params);
	}    
  
  
	public Constrain notVar(Object p) {
		if (p instanceof ParamBoolean)
			return new NotVar((ParamBoolean)p);

		return null;
	}
	
	/**
	 * Add constraint (r2 p c) to wrapper p.
	 */
	public Result r2VarConst(String r2, Object p, String c) {
		int r = Integer.parseInt(r2);
		if (c==null || !(p instanceof Param)) {return null;}	
			
		if (isIntegralFun(r)) {  //fApp
     	if (p instanceof ParamIntegral) {
     		ParamIntegral pInt = (ParamIntegral) p;
     		X parsedConst = pInt.parse(c);
     		if (parsedConst==null) {  //outside our domain
     			return null;
     		}
     		return new F2App(pInt.getVar(), r, parsedConst, pInt);
     	}
			return null;
    }
		return new R2VarConst((Param) p, r, c);
	}	
	
	
	/**
	 *  Add constraint (r2 c p) to wrapper p.
	 */
	public Result r2ConstVar(String r2, String c, Object p) {
		int r = Integer.parseInt(r2);
		if (c==null || !(p instanceof Param)) {return null;}	
			
		if (isIntegralFun(r)) {  //fApp
     	if (p instanceof ParamIntegral) {
     		ParamIntegral pInt = (ParamIntegral) p;
     		X parsedConst = pInt.parse(c);
     		if (parsedConst==null) {  //outside our domain
     			return null;
     		}     		
     		return new F2App(parsedConst, r, pInt.getVar(), pInt);
     	}
			return null;
    }
		return new R2ConstVar(c, r, (Param)p);
	}
	
	
	/**
	 * Add constraint (r2 p1 p2) only to p1.
	 */
	public Result r2VarVar(String r2, Object p1, Object p2) {
		int r = Integer.parseInt(r2);
		if (p2==null || !(p1 instanceof Param)) {return null;}
		
    if (p2 instanceof String) { //p2 is a temp var to be created.
      String n2 = (String) p2;
      /* Create Param p2 of same type as p1. */
      p2 = createParam(((Param)p1).getType(), n2);
      addParam(n2, (Param) p2);
    }
    
    if (isIntegralFun(r)) {  //fun
    	/* var1 * var2 takes our linear(!) int solver too long to solve. */
    	if (r==sym.STAR || r==sym.SLASH || 
    			!(p1 instanceof ParamIntegral) || !(p2 instanceof ParamIntegral))
        return null;
    	
			return new F2App(
					((ParamIntegral)p1).getVar(), r, ((ParamIntegral)p2).getVar(), ((ParamIntegral)p2));
    }
		return new R2VarVar((Param)p1, r, (Param)p2);
	}

	 /* (< (* |width:73.15| |height:73.26|) 0) */
	public Result r2rAppConst(String r2, Result r2App, String c) {
		int r = Integer.parseInt(r2);
		if (r2App==null || c==null) {return null;}
		
		if (r2App instanceof Constrain) {  //(r (rel x y) z)
			return null;
		}
		
		assert (r2App instanceof F2App);
		F2App f2App = (F2App) r2App;
 		X parsedConst = f2App.parse(c);
 		if (parsedConst==null) {  //outside our domain
 			return null;
 		}		
		
		if (isIntegralFun(r))  //(fun (fun x y) z)
			return new F2App(f2App.apply(), r, parsedConst, f2App.getConstParser());	

		//(rel (fun x y) z)
		return new R2XX(f2App.apply(), r, parsedConst);
	}

	public Result r2rAppVar(String r2, Result r2App, Object p) {
		int r = Integer.parseInt(r2);
		if (r2App==null || !(p instanceof ParamIntegral)) {return null;}

		if (r2App instanceof Constrain) {  //(r (rel x y) z)
			return null;
		}

		assert (r2App instanceof F2App);
		F2App f2App = (F2App) r2App;
		ParamIntegral pInt = (ParamIntegral) p;
		if (isIntegralFun(r)) {  //(fun (fun x y) z)
			return new F2App(f2App.apply(), r, pInt.getVar(), pInt);	
		}
		
		//(rel (fun x y) z)
		return new R2XX(f2App.apply(), r, pInt.getVar());
	}
  

	public static void constrain(X lhs, int r2, X rhs) {
		/* Mapping: parsed Simplify --> POOC. */
		switch (r2) {
			case sym.EQ  : ParamIntegral.getSolver().eq (lhs,rhs); break;
			case sym.NEQ : ParamIntegral.getSolver().neq(lhs,rhs); break;
			case sym.G   : ParamIntegral.getSolver().gt (lhs,rhs); break;
			case sym.GEQ : ParamIntegral.getSolver().geq(lhs,rhs); break;
			case sym.L   : ParamIntegral.getSolver().lt (lhs,rhs); break;
			case sym.LEQ : ParamIntegral.getSolver().leq(lhs,rhs); break;  
		}		
	}
	
	/* is r a supported arithmetic function +, -, *, / */
	protected boolean isIntegralFun(int r) {
		return r==sym.STAR || r==sym.SLASH || r==sym.PLUS || r==sym.MINUS;
	}
	
	
	/**
	 * (select (select elems |a:137.40|) 0)
	 * 
	 * accesses array a at location b.  
	 * 
	 * @param b is null | String. 
	 * @return element of array a at position b.
	 */
	public Param arrayAtConst(ParamArray a, String b) {
    if (a==null) {return null;} //not interested.

    
      Param res = a.getElemValue(b);
      if (res==null) {
        a.addElem(b, createParam(a.getType().getComponentType(), b));
        res = a.getElemValue(b);
      }
      assert res!=null;
      return res;
	}
	
	/**
	 * (select (select elems |a:137.40|) |b:137.47|)
	 * 
	 * accesses array a:137.40 at the named location b:137.47.
	 * The int-solver will later resolve b:137.47 to some int.  
	 * 
	 * @param b is null | String | ParamIntegral. 
	 * @return element of array a at position b.
	 */
	public Param arrayAtVar(ParamArray a, Object b) {
    if (a==null || b==null) {return null;} //not interested.
    
      if (b instanceof String) {  //new local variable.
        String name = (String) b;
        b = new ParamInt(0, ParamInt.getDomain().getMax(), name);  //FIXME assume int, might be byte.
        addParam(name, (ParamInt) b);  //register variable b.
      }      
      Param res = a.getElem((ParamIntegral)b);  //might be accessed via byte.
      if (res==null) {
        a.addElem((ParamIntegral)b, createParam(a.getType().getComponentType(), "?"));
        res = a.getElem((ParamIntegral)b);
      }
      assert (res != null);
      return res;
	}
	
	
	/**
	 * o.f --> (select |f:19.7| |o:26.19|)
	 * 
	 * @return null | Param;
	 */
	protected Param selectVarVar(String f, String o) {
		if (o==null || f==null) {           //not interested
			return null;
		}
		
		//o != null.
			Param param = params.get(o);
			if (param==null)
				return null;  //could not infer its type in firstpass.
			
			//(o --> Param param)
			Param res = null;
			
			if (param instanceof ParamArray) {//TODO any array member of interest?
				//ParamArray oWrapper = (ParamArray) param;
				return null;
			}
      
			/* (o --> ParamComplex oWrapper) */
			if (param instanceof ParamComplex) {
				ParamComplex oWrapper = (ParamComplex) param;
				assert (oWrapper.getType() != null); //oWrapper's type already defined.				        
				res = (Param) oWrapper.getElemValue((String)f);
				String fieldName = stripEscPostfix((String)f);
				
				/* (String f --> Param field) not yet in oWrapper? */
				if (res==null) {
					Class fieldType = null;
					try {
						fieldType = oWrapper.getType().getDeclaredField(fieldName).getType();
					}
					catch (NoSuchFieldException e) {
						return null;  //reflection cannot find LS field in this.(LS<1>).
					}
					Param field = createParam(fieldType, f);
					oWrapper.addElem((String)f, field);
					res = field;
				}
			}
			return res;      
		}				
	
	
	protected static String rel(int r) {
		switch (r) {
			case sym.EQ  : return "=";
			case sym.NEQ : return "!=";
			case sym.G   : return ">";
			case sym.GEQ : return ">=";
			case sym.L   : return "<";
			case sym.LEQ : return "<=";
			case sym.STAR	: return "*";
			case sym.SLASH: return "/";
			case sym.PLUS : return "+";
			case sym.MINUS: return "-";
			default: return "no such relation";
		}
	}	
}
